package com.xiaomaoguai.fcp.pre.kepler.tddl.sequence;

import com.google.common.collect.Lists;
import com.taobao.tddl.client.sequence.exception.SequenceException;
import com.taobao.tddl.client.sequence.impl.GroupSequence;
import com.taobao.tddl.client.sequence.impl.GroupSequenceDao;
import com.xiaomaoguai.fcp.pre.kepler.tddl.config.TddlMultiProperties;
import com.xiaomaoguai.fcp.pre.kepler.tddl.config.TddlProperties;
import com.xiaomaoguai.fcp.pre.kepler.tddl.config.TddlSequenceProperties;
import com.xiaomaoguai.fcp.pre.kepler.tddl.exception.TddlRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * TDDL 序列工具类
 *
 * @author chenyao
 * @since 2018年6月4日 下午10:09:34
 */
@Slf4j
public class TddlSequenceUtils {

	/**
	 * 构建 TDDL GroupSequenceDao 实例对象
	 *
	 * @param tddlAppName    TDDL appName
	 * @param tddlGroupName  TDDL groupName
	 * @param sequenceConfig TDDL Sequence 属性配置
	 * @param init           是否进行初始化
	 * @return TDDL GroupSequenceDao 实例对象
	 */
	public static GroupSequenceDao buildSequenceDao(String tddlAppName, String tddlGroupName,
													TddlSequenceProperties sequenceConfig, boolean init) {
		GroupSequenceDao sequenceDao = new GroupSequenceDao();
		// appName，必填
		sequenceDao.setAppName(tddlAppName);
		// 数据源的个数
		sequenceDao.setDscount(sequenceConfig.getDsCount());
		// 数据库名：从哪些数据库中取ID
		// 如果在末尾插入"-OFF",该源将被关掉，该源占据的SQL段会被保留"
		// 当dbGroupKeys中配置的个数小于dbcount的值的时候，默认配置了"-OFF"的源
		sequenceDao.setDbGroupKeys(Lists.newArrayList(tddlGroupName));
		// 内步长 ,默认为1000，取值在1-100000之间
		sequenceDao.setInnerStep(sequenceConfig.getInnerStep());
		// 重试次数，在多个groupDataSource的场景下，建议设置成1-2次。默认为2次
		sequenceDao.setRetryTimes(sequenceConfig.getRetryTimes());
		// 使用的表的表名 ，默认为sequence
		sequenceDao.setTableName(sequenceConfig.getTableName());
		// id生成器的字段名,默认为name
		sequenceDao.setNameColumnName(sequenceConfig.getNameColumnName());
		// 存值的列的字段名,默认为value
		sequenceDao.setValueColumnName(sequenceConfig.getValueColumnName());
		// 存修改时间的字段名 ,默认为gmt_modified
		sequenceDao.setGmtModifiedColumnName(sequenceConfig.getGmtModifiedColumnName());
		sequenceDao.setAdjust(sequenceConfig.isAdjust());

		if (init) {
			try {
				sequenceDao.init();
			} catch (SequenceException ex) {
				throw new TddlRuntimeException("init TDDL GroupSequenceDao failed, tddlAppName: " + tddlAppName
						+ ", tddlGroupName: " + tddlGroupName + ", properties: " + sequenceConfig, ex);
			}
		}
		return sequenceDao;
	}

	/**
	 * 构建 TDDL GroupSequenceDao 实例对象
	 *
	 * @param properties TDDL 属性配置
	 * @param init       是否进行初始化
	 * @return TDDL GroupSequenceDao 实例对象
	 */
	public static GroupSequenceDao buildSequenceDao(TddlProperties properties, boolean init) {
		String tddlAppName = properties.getAppName();
		String tddlGroupName = properties.getGroupName();
		TddlSequenceProperties sequenceConfig = properties.getSequenceConfig();
		return buildSequenceDao(tddlAppName, tddlGroupName, sequenceConfig, init);
	}

	/**
	 * 构建 TDDL GroupSequenceDao 实例对象
	 *
	 * @param properties TDDL 属性配置
	 * @param init 是否进行初始化
	 * @return TDDL GroupSequenceDao 实例对象
	 */
	public static GroupSequenceDao buildSequenceDao(TddlMultiProperties.TddlProperty properties, boolean init) {
		String tddlAppName = properties.getAppName();
		String tddlGroupName = properties.getGroupName();
		TddlSequenceProperties sequenceConfig = properties.getSequenceConfig();
		return buildSequenceDao(tddlAppName, tddlGroupName, sequenceConfig, init);
	}

	/**
	 * 初始化 TDDL 序列生成器，并注册到 Spring Application Context 中
	 *
	 * @param sequences 待初始化的 Sequence 实例集合
	 * @param groupSequenceDao TDDL GroupSequenceDao 实例对象
	 * @param applicationContext Spring Application Context
	 */
	public static void initTddlSequence(Collection<Sequence> sequences, GroupSequenceDao groupSequenceDao,
										ApplicationContext applicationContext) {
		Map<String, GroupSequence> map = new HashMap<>(16);
		for (Sequence value : sequences) {
			if (!(value instanceof TddlSequence)) {
				continue;
			}
			TddlSequence tddlSequence = (TddlSequence) value;
			if (tddlSequence.getSequence() != null) {
				continue;
			}
			//TODO
			//TableSequence tableSequence = tddlSequence.getTableInfo().getTableSequence();
			String sequenceName = "tableSequence.getSequenceName()";
			if (StringUtils.isBlank(sequenceName)) {
				throw new NullPointerException("sequenceName is null, tableInfo: " + tddlSequence.getTableInfo());
			}
			if (map.containsKey(sequenceName)) {
				GroupSequence sequence = map.get(sequenceName);
				tddlSequence.setSequence(sequence);
				continue;
			}
			GroupSequence sequence = initTddlSequence(applicationContext, sequenceName, null, groupSequenceDao);
			if (sequence == null) {
				throw new TddlRuntimeException("create TDDL sequence failed, sequenceName = " + sequenceName
						+ ", tableName = " + tddlSequence.getTableInfo().getTableName());
			}
			tddlSequence.setSequence(sequence);
			map.put(sequenceName, sequence);
		}
	}

	/**
	 * 初始化 TDDL 序列生成器，并注册到 Spring Application Context 中
	 *
	 * @param applicationContext       Spring Application Context
	 * @param sequenceName             待初始化的 TDDL sequence 名称
	 * @param groupSequenceDaoBeanName TDDL GroupSequenceDao 实例对象Bean Name【参数
	 *                                 {@code groupSequenceDaoBeanName} 和 {@code groupSequenceDao}
	 *                                 二选一】
	 * @param groupSequenceDao         TDDL GroupSequenceDao 实例对象【参数
	 *                                 {@code groupSequenceDaoBeanName} 和 {@code groupSequenceDao}
	 *                                 二选一】
	 * @return 生成的 TDDL 序列生成器
	 */
	public static GroupSequence initTddlSequence(ApplicationContext applicationContext, String sequenceName,
												 String groupSequenceDaoBeanName, GroupSequenceDao groupSequenceDao) {
		if (applicationContext.containsBean(sequenceName)) {
			return applicationContext.getBean(sequenceName, GroupSequence.class);
		}
		if (groupSequenceDao != null || StringUtils.isNotBlank(groupSequenceDaoBeanName)) {
			try {
				// 创建Bean
				DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
						.getAutowireCapableBeanFactory();
				BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
						.genericBeanDefinition(GroupSequence.class);
				beanDefinitionBuilder.addPropertyValue("name", sequenceName);
				if (groupSequenceDao != null) {
					beanDefinitionBuilder.addPropertyValue("sequenceDao", groupSequenceDao);
				} else {
					beanDefinitionBuilder.addPropertyValue("sequenceDao",
							new RuntimeBeanReference(groupSequenceDaoBeanName));
				}
				AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
				beanDefinition.setInitMethodName("init");

				// 注册Bean
				log.info("register TDDL sequence: {}", sequenceName);
				beanFactory.registerBeanDefinition(sequenceName, beanDefinition);
				return applicationContext.getBean(sequenceName, GroupSequence.class);
			} catch (Exception ex) {
				throw new TddlRuntimeException("create TDDL sequence failed, sequenceName = " + sequenceName, ex);
			}
		}
		return null;
	}

}
